/*******************************************************************************
Copyright (c) 2005-2009 David Williams

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.

    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.

    3. This notice may not be removed or altered from any source
    distribution.
*******************************************************************************/

#ifndef __PolyVox_MeshDecimator_H__
#define __PolyVox_MeshDecimator_H__

#include "PolyVoxCore/SurfaceMesh.h"
#include "PolyVoxCore/Vector.h"
#include "PolyVoxCore/VertexTypes.h"

#include <bitset>
#include <vector>

namespace PolyVox
{
	/// The MeshDecimator reduces the number of triangles in a mesh.
	////////////////////////////////////////////////////////////////////////////////
	/// Meshes generated by the PolyVox surface extractors typically have a very high
	/// number of triangles in them. This can pose difficulties both for the rendering
	/// storage of such meshes. The MeshDecimator provides a way of reducing the triangle
	/// count with minimal visual effect.
	///
	/// The MeshDecimator is based on the principle of edge collapse, and currently works
	/// with meshes generated by the SurfaceExtractor or CubicSurfaceExtractor. It does
	/// not work with meshes generated by the CubicSurfaceExtractorWithNormals, although
	/// this may be addressed in the future. The algorithm iterates over each pair of 
	/// connected vertices in the mesh and attemps to determine if they can be collapsed
	/// into a single vertex.
	///
	/// The main criteria used in deciding whether two vertices can collapse is whether
	/// they have the same normal. In the case of the cubic surfaces the normals must be
	/// exactly the same, whereas in the case of the Marching Cubes surfaces a threshold
	/// is used to determine whether two normals are 'close enough'. Additional constraints
	/// apply to vertices which lie on the edges of regions or on the boundary between two
	/// regions - these vertices are much less likely to be collapsed.
	///
	/// Given a mesh called 'mesh', you can create a decimated version as follows:
	/// \code
	/// SurfaceMesh<PositionMaterial> decimatedMesh;
	/// MeshDecimator<PositionMaterial> decimator(&mesh, &decimatedMesh);
	/// decimator.execute();
	/// \endcode
	/// 
	/// The above applies for a cubic mesh, for a Marching Cubes mesh you need to parametise
	/// the MeshDecimator and resulting SurfaceMesh on the 'PositionMaterialNormal' type
	/// instead of the 'PositionMaterial' type.
	template <typename VertexType>
	class MeshDecimator
	{
		//Used to keep track of when a vertex is
		//on one or more faces of  the region
		enum RegionFaceFlags
		{
			RFF_ON_REGION_FACE_NEG_X,
			RFF_ON_REGION_FACE_POS_X ,
			RFF_ON_REGION_FACE_NEG_Y ,
			RFF_ON_REGION_FACE_POS_Y ,
			RFF_ON_REGION_FACE_NEG_Z ,
			RFF_ON_REGION_FACE_POS_Z,
			RFF_NO_OF_REGION_FACE_FLAGS
		};

		//Data about the initial mesh - this
		//will be fill in once at the start
		struct InitialVertexMetadata
		{
			Vector3DFloat normal;
			bool isOnMaterialEdge;
			std::bitset<RFF_NO_OF_REGION_FACE_FLAGS> isOnRegionFace;
		};

		//Representing a triangle for decimation purposes.
		struct Triangle
		{
			uint32_t v0;
			uint32_t v1;
			uint32_t v2;
			Vector3DFloat normal;
		};

		struct IntVertex
		{			
			int32_t x;
			int32_t y;
			int32_t z;
			uint32_t index;

			IntVertex(int32_t xVal, int32_t yVal, int32_t zVal, uint32_t indexVal)
				:x(xVal)
				,y(yVal)
				,z(zVal)
				,index(indexVal)
			{
			}

			bool operator==(const IntVertex& rhs) const
			{
				return (x == rhs.x) && (y == rhs.y) && (z == rhs.z);
			}

			bool operator<(const IntVertex& rhs) const
			{
				if (z < rhs.z)
					return true;
				if (rhs.z < z)
					return false;

				if (y < rhs.y)
					return true;
				if (rhs.y < y)
					return false;

				if (x < rhs.x)
					return true;
				if (rhs.x < x)
					return false;

				return false;
			}
		};

	public:
		///Constructor
		MeshDecimator(const SurfaceMesh<VertexType>* pInputMesh, SurfaceMesh<VertexType>* pOutputMesh, float fEdgeCollapseThreshold = 0.95f);

		///Performs the decimation.
		void execute();

	private:

		void fillInitialVertexMetadata(std::vector<InitialVertexMetadata>& vecInitialVertexMetadata);

		void buildConnectivityData(void);

		bool attemptEdgeCollapse(uint32_t uSrc, uint32_t uDest);

		const SurfaceMesh<VertexType>* m_pInputMesh;
		SurfaceMesh<VertexType>* m_pOutputMesh;

		uint32_t performDecimationPass(float m_fMinDotProductForCollapse);
		bool isSubset(std::bitset<RFF_NO_OF_REGION_FACE_FLAGS> a, std::bitset<RFF_NO_OF_REGION_FACE_FLAGS> b);

		bool canCollapseEdge(uint32_t uSrc, uint32_t uDest);
		bool canCollapseNormalEdge(uint32_t uSrc, uint32_t uDst);
		bool canCollapseRegionEdge(uint32_t uSrc, uint32_t uDst);
		bool canCollapseMaterialEdge(uint32_t uSrc, uint32_t uDst);
		bool collapseChangesFaceNormals(uint32_t uSrc, uint32_t uDst, float fThreshold);

		//Data structures used during decimation

		std::vector<bool> vertexLocked;
		std::vector<uint32_t> vertexMapper;

		std::vector<Triangle> m_vecTriangles;
		std::vector< std::vector<uint32_t> > trianglesUsingVertex; //Should probably use vector of vectors, and resise in advance.

		std::vector<InitialVertexMetadata> m_vecInitialVertexMetadata;

		float m_fMinDotProductForCollapse;
	};
}

#include "PolyVoxCore/MeshDecimator.inl"

#endif //__PolyVox_MeshDecimator_H__
